using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Xml;
using MJB.UI.ContentEditor.Document;
using MJB.UI.ContentEditor.UserControls;

namespace MJB.UI.ContentEditor.Windows
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Member Variables
        #region Dependency Properties

        /// <summary>
        /// This is the dependency property that exposes the displayed reader property.
        /// </summary>
        private static readonly DependencyProperty DisplayedReaderProperty = DependencyProperty.Register("DisplayedReader",
            typeof(IDocumentReader),
            typeof(MainWindow),
            new PropertyMetadata(new PropertyChangedCallback(OnDisplayedReaderPropertyChanged)));

        #endregion

        /// <summary>
        /// A list of document readers.
        /// </summary>
        private ObservableCollection<IDocumentReader> mReaders;

        /// <summary>
        /// This dictionary maintains a mapping from document readers to 
        /// tab item controls.
        /// </summary>
        private Dictionary<IDocumentReader, TabItemControl> mTabItemControls;

        /// <summary>
        /// These are the settings that we use to read and write data.
        /// </summary>
        private EditorSettings mSettings;

        /// <summary>
        /// This is the management service that we use to connect to the remote server.
        /// </summary>
        private XmlManagementService mManagementService;

        #endregion
        #region Constructors

        /// <summary>
        /// Create some data:
        /// </summary>
        public MainWindow()
        {
            mReaders = new ObservableCollection<IDocumentReader>();
            mTabItemControls = new Dictionary<IDocumentReader, TabItemControl>();

            mSettings = new EditorSettings();
            mSettings.LoadFromRegistry();

            InitializeComponent();
            mReaders.CollectionChanged += new NotifyCollectionChangedEventHandler(OnReadersCollectionChanged);

        }

        #endregion
        #region Public Properties

        /// <summary>
        /// Gets / sets the description.
        /// </summary>
        public IDocumentReader DisplayedReader
        {
            get
            {
                return (IDocumentReader)GetValue(DisplayedReaderProperty);
            }
            set
            {
                SetValue(DisplayedReaderProperty, value);
            }
        }

        #endregion
        #region Public Methods

        /// <summary>
        /// Removes a document reader from the list.
        /// </summary>
        /// <param name="reader"></param>
        public void RemoveDocumentReader(IDocumentReader reader)
        {
            mReaders.Remove(reader);
        }

        #endregion
        #region Event Handlers
        #region Other Event Handlers

        /// <summary>
        /// When the readers collection is changed.  Add or remove any 
        /// tab pages from the tab control, according to what has changed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnReadersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            mSaveButton.IsEnabled = (mReaders.Count > 0);

            if(e.Action == NotifyCollectionChangedAction.Add)
            {
                // They like to throw curve balls at us sometimes....
                if(e.NewItems != null)
                {
                    // For each new reader, add a new tab item.  It 
                    // would really be nice if this could work as a data template - 
                    // but alas, doing so would cause there to be only one INSTANCE
                    // of each of these controls - not what we want!  We want each
                    // tab to have its own state.  Otherwise, it looks funky when 
                    // you go back and forth between tabs.
                    foreach(IDocumentReader reader in e.NewItems)
                    {
                        TabItem tab = new TabItem();
                        tab.DataContext = reader;

                        tab.Header = new TabItemHeaderControl();
                        tab.Content = new TabItemControl();

                        mTabControl.Items.Add(tab);
                        mTabControl.SelectedItem = tab;

                        mTabItemControls[reader] = tab.Content as TabItemControl;

                    }

                }

            }
            else if(e.Action == NotifyCollectionChangedAction.Remove)
            {
                if(e.OldItems != null)
                {
                    // Find all of the tabs that should be removed.
                    var itemsToRemove = (from TabItem t in mTabControl.Items
                                         let reader = t.DataContext as IDocumentReader
                                         where e.OldItems.Contains(reader)
                                         select new
                                         {
                                             Tab = t,
                                             Reader = reader
                                         }).ToList();

                    itemsToRemove.ForEach(v =>
                    {
                        mTabControl.Items.Remove(v.Tab);
                        mTabItemControls.Remove(v.Reader);
                    });

                }

            }

        }

        /// <summary>
        /// Load the window.  The first thing we will do is load the initial files and directories.
        /// </summary>
        /// <param name="o">The object sending the event.</param>
        /// <param name="args">The arguments of the event.</param>
        public void OnWindowLoaded(object o, EventArgs args)
        {
            // Get the settings:
            while(mSettings.XmlDirectory.Length == 0)
            {

                "Select a root directory for the XML content.".DisplayMessageDialog();

                SettingsWindow settingsWindow = new SettingsWindow();
                settingsWindow.Owner = Application.Current.MainWindow;
                settingsWindow.ShowDialog();

                mSettings.LoadFromRegistry();

            }

            // Create the management service object and load the settings:
            while(true)
            {
                try
                {
                    mManagementService = new XmlManagementService();

                    FileNode directoryNode = new FileNode();
                    directoryNode.ChildNodes.Add(GenerateNode(String.Empty));
                    mTreeView.ItemsSource = directoryNode.ChildNodes;
                    break;

                }
                catch
                {
                    mTreeView.ItemsSource = null;

                    StringBuilder messageBoxBuilder = new StringBuilder();
                    messageBoxBuilder.Append("There was an error connecting to the remote server.");
                    messageBoxBuilder.Append(Environment.NewLine);
                    messageBoxBuilder.Append("Change connection settings and try again?");

                    // Throw up the settings window and give the user the chance to enter a new URL,
                    // user name or password.
                    if(messageBoxBuilder.DisplayQuestionDialog())
                    {
                        SettingsWindow settingsWindow = new SettingsWindow();
                        settingsWindow.Owner = Application.Current.MainWindow;
                        settingsWindow.ShowDialog();
                        mSettings.LoadFromRegistry();
                        continue;

                    }
                    else
                        this.Close();

                }

            }

        }

        /// <summary>
        /// This is called when a tree view item is expanded.
        /// </summary>
        /// <param name="o"></param>
        /// <param name="args"></param>
        public void OnTreeViewItemExpanded(object o, RoutedEventArgs args)
        {
            TreeViewItem treeViewItem = (TreeViewItem)o;
            FileNode fileNode = (FileNode)treeViewItem.DataContext;

            // Only do anything on place holders:
            if(fileNode.ChildNodes.Count == 1)
            {
                if(fileNode.ChildNodes[0].Name == "...")
                {
                    Cursor originalCursor = this.Cursor;
                    this.Cursor = Cursors.Wait;
                    try
                    {
                        FileNode childNode = GenerateNode(fileNode.Path);
                        fileNode.ChildNodes.Clear();

                        // Port all of the nodes from the returned node 
                        // over to the existing tree view node:
                        foreach(FileNode childNodeChildNode in childNode.ChildNodes)
                        {
                            childNodeChildNode.ParentNode = fileNode;
                            fileNode.ChildNodes.Add(childNodeChildNode);
                        }

                    }
                    catch(Exception e)
                    {
                        e.DisplayError(new StringBuilder("There was an error expanding the node at '")
                                                 .Append(fileNode.Path)
                                                 .Append("'.")
                                                 .Append(Environment.NewLine));
                    }
                    finally
                    {
                        this.Cursor = originalCursor;
                    }

                }

            }

        }


        /// <summary>
        /// This is called when a tree view item is double clicked.
        /// </summary>
        /// <param name="o"></param>
        /// <param name="args"></param>
        public void OnTreeViewDoubleClicked(object o, EventArgs args)
        {
            FileNode selectedFile = (FileNode)mTreeView.SelectedItem;
            if(mTreeView.SelectedItem == null)
                return;

            if(selectedFile.ChildNodes.Count > 0)
                return;

            // Potentially, I suppose we could have somebody create a new file 
            // called "..." which would cause a problem...
            if(selectedFile.Name == "...")
                return;

            // See if we're already displaying this guy:
            IDocumentReader reader = (from TabItem tab in mTabControl.Items
                                      let documentReader = tab.Header as IDocumentReader
                                      where documentReader != null
                                      where documentReader.FilePath == selectedFile.Path
                                      select documentReader).FirstOrDefault();

            if(reader != null)
            {
                this.DisplayedReader = reader;
                return;
            }

            Cursor originalCursor = this.Cursor;
            this.Cursor = Cursors.Wait;

            try
            {
                reader = mManagementService.GetFile(selectedFile.Path);
                reader.FilePath = selectedFile.Path;
                reader.FileName = selectedFile.Name;

                mReaders.Add(reader);
                this.DisplayedReader = reader;

            }
            catch(Exception e)
            {
                e.DisplayError(new StringBuilder("There was an error opening the file '")
                                         .Append(selectedFile.Path)
                                         .Append("'.")
                                         .Append(Environment.NewLine));
            }
            finally
            {
                this.Cursor = originalCursor;
            }

        }

        /// <summary>
        /// Called when the selection changes on the tab control. Make sure that we update the DisplayedReader!
        /// </summary>
        /// <param name="sender">The object generating the event.</param>
        /// <param name="e">The arguments of the event.</param>
        private void OnTabControlSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            TabItem tabItem = mTabControl.SelectedItem as TabItem;

            if(tabItem != null)
            {
                this.DisplayedReader = tabItem.DataContext as IDocumentReader;
            }

        }

        #endregion
        #endregion
        #region Public Methods

        /// <summary>
        /// This method will reload an open document, if it is currently open.
        /// </summary>
        /// <param name="path">The path of the remote file to open.</param>
        public void ReloadOpenDocument(string path)
        {
            IDocumentReader oldReader = (from TabItem tab in mTabControl.Items
                                         let documentReader = tab.DataContext as IDocumentReader
                                         where documentReader != null
                                         where documentReader.FilePath == path
                                         select documentReader).FirstOrDefault();

            if(oldReader != null)
            {
                // Update the document:
                IDocumentReader newReader = mManagementService.GetFile(path);

                // Copy the new reader into the old one.  This way, the values of 
                // the new reader will update the controls that are bound to the old one:
                StringBuilder stringBuilder = new StringBuilder();
                using(XmlTextWriter textWriter = new XmlTextWriter(new StringWriter(stringBuilder)))
                    newReader.WriteXml(textWriter);

                using(XmlTextReader textReader = new XmlTextReader(new StringReader(stringBuilder.ToString())))
                    oldReader.ReadXml(textReader);

            }

        }

        /// <summary>
        /// Sends a file to the server.
        /// </summary>
        /// <param name="fileNode"></param>
        /// <param name="path"></param>
        public void SendFileToServer(string serverPath, string physicalPath)
        {
            IDocumentReader reader = null;
            byte[] stringBytes = null;

            // This will open the file so that we can read the contents:
            using(StreamReader streamReader = new StreamReader(new FileStream(physicalPath, FileMode.Open)))
                stringBytes = Encoding.UTF8.GetBytes(streamReader.ReadToEnd());

            // This will create a reader that can read this version of the file:
            using(MemoryStream memoryStream = new MemoryStream(stringBytes))
                reader = DocumentReaderFactory.GetReader(memoryStream);

            using(XmlTextReader textReader = new XmlTextReader(new MemoryStream(stringBytes)))
            {
                // Now we can read the file and post it up to the website:
                reader.ReadXml(textReader);
                mManagementService.UpdateFile(serverPath, reader);
            }

        }

        /// <summary>
        /// Selects the entered IDocumentReader.
        /// </summary>
        /// <param name="reader"></param>
        public void SelectDocument(IDocumentReader reader)
        {

            mTabControl.SelectedItem = (from TabItem tab in mTabControl.Items
                                        let currentReader = tab.DataContext as IDocumentReader
                                        where currentReader != null
                                        where currentReader == reader
                                        select tab).FirstOrDefault();
        }

        /// <summary>
        /// Returns the physical path for a given child node.
        /// </summary>
        /// <param name="fileNode"></param>
        /// <returns></returns>
        public string GetPhysicalPathForFileNode(FileNode fileNode)
        {
            string path = mSettings.XmlDirectory;
            var query = from part in fileNode.Path.Split('/')
                        where part.Length > 0
                        select part;

            query.ForEach(p => path = System.IO.Path.Combine(path, p));
            return path;

        }

        /// <summary>
        /// Converts the physical path on the disc drive to a path on the server.
        /// </summary>
        /// <param name="physicalPath"></param>
        /// <returns></returns>
        public string GetServerPathForPhysicalPath(string physicalPath)
        {
            string path = physicalPath.Substring(mSettings.XmlDirectory.Length);
            return path.Replace(System.IO.Path.DirectorySeparatorChar, '/');
        }

        /// <summary>
        /// Generates a tree node based on a directory.
        /// </summary>
        /// <param name="directory"></param>
        /// <returns></returns>
        public FileNode GenerateNode(string path)
        {
            FileNode fileNode = new FileNode();
            fileNode.Path = (path.Length == 0) ? String.Empty : path;
            fileNode.IsDirectory = true;
            fileNode.Name = (path.Length == 0) ? "XML" : path.Substring(path.LastIndexOf("/") + 1);

            // First add directories:
            List<string> subDirectories = mManagementService.GetDirectories(path);
            foreach(string subDirectory in subDirectories)
            {
                FileNode subDirectoryNode = new FileNode();
                subDirectoryNode.IsDirectory = true;
                subDirectoryNode.Path = subDirectory;
                subDirectoryNode.Name = subDirectory.Substring(subDirectory.LastIndexOf("/") + 1);
                subDirectoryNode.ParentNode = fileNode;

                // Add a place holder:
                FileNode placeHolder = new FileNode();
                placeHolder.Path = "...";
                placeHolder.Name = "...";
                subDirectoryNode.ChildNodes.Add(placeHolder);

                // Add to the root node:
                fileNode.ChildNodes.Add(subDirectoryNode);
            }

            List<string> files = mManagementService.GetFiles(path);
            foreach(string filePath in files)
            {
                FileNode childFileNode = new FileNode();
                childFileNode.IsDirectory = false;
                childFileNode.Path = filePath;

                string fileName = filePath.Substring(filePath.LastIndexOf("/") + 1);
                fileName = fileName.Substring(0, fileName.IndexOf("."));

                childFileNode.Name = fileName;
                childFileNode.ParentNode = fileNode;

                // Add to the root node:
                fileNode.ChildNodes.Add(childFileNode);
            }

            return fileNode;

        }

        #endregion
        #region Dependency Property Events

        /// <summary>
        /// This is called whenever the displayed reader property changes on the displayed readers collection.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        public static void OnDisplayedReaderPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            MainWindow window = (MainWindow)sender;
            window.SelectDocument((IDocumentReader)args.NewValue);
        }

        #endregion

    }

}
